using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using CSScriptLibrary;

internal class Job
{
    public static void Test()
    {
AppDomain remote = AppDomain.CurrentDomain.Clone();
        
remote.Execute(() =>
{
    var Sum = CSScript.BuildEval(@"func(float a, float b) {
                                            return a + b;
                                    }");
                                                
    var Average = CSScript.BuildEval(@"func(float a, float b) {
                                            return (a + b)/2;
                                        }");
                                               
    Console.WriteLine("Sum = {0}\nAverage={1}", Sum(1f, 2f), Average(1f, 2f));
});
        
remote.Unload();
        return;
        /// 
        //string asmFile = Assembly.GetExecutingAssembly().Location;
        //using (AsmHelper helper = new AsmHelper(asmFile, null, false))
        //{
        //    helper.Invoke("*.Execute");
        //}

        //new RemoteExecutor().Execute(() => Console.WriteLine("tttt"));
        //RemoteDomain domain = new
        //.Execute(() => Console.WriteLine("tttt"));

        AppDomain remote1 = AppDomain.CurrentDomain.Clone("test domain");
        
        remote1.Execute(() =>
        {
            Console.WriteLine("--- ttt ---");
        });

        remote1.Execute(() =>
        {
            Console.WriteLine("--- aaa ---");
        });

        remote.Execute(() =>
        {
            var PrintDiv = CSScript.BuildEval(@"func(int a, int b) {
                                                    Console.WriteLine((a/b));
                                                }");
            PrintDiv(6, 2);
        });

        remote1.Unload();

        AppDomainHelper.ExecuteAndUnload(() =>
            {
                var PrintDiv = CSScript.BuildEval(@"func(int a, int b) {
                                                    Console.WriteLine((a/b));
                                                }");
                PrintDiv(99, 3);
            });

Action job = () =>
        {
            var PrintDiv = CSScript.BuildEval(@"func(int a, int b) {
                                        Console.WriteLine((a/b));
                                    }");
            PrintDiv(999, 3);
        };

AppDomain.CurrentDomain
        .Clone()
        .Execute(job)
        .Unload();

    }

    static public void Execute()
    {
        var PrintSum = CSScript.LoadMethod(
                                @"public static void PrintSum(int a, int b)
                                  {
                                      Console.WriteLine((a+b));
                                  }")
                                  .GetStaticMethod();
        PrintSum(1, 2);

        var PrintDiv = CSScript.BuildEval(@"func(int a, int b) {
                                               Console.WriteLine((a/b));
                                            }");
        PrintDiv(6, 2);
    }
}

internal static class AppDomainHelper
{
    private class RemoteExecutor1 : MarshalByRefObject
    {
        public void Execute(Action action)
        {
            //Console.WriteLine("Executing from " + AppDomain.CurrentDomain.FriendlyName);
            action();
        }
    }

    public static AppDomain Execute(this AppDomain domain, Action action)
    {
        var remote = (RemoteExecutor1)domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(RemoteExecutor1).ToString());
        remote.Execute(action);
        return domain;
    }

    public static void ExecuteAndUnload(Action action)
    {
        AppDomain.CurrentDomain
                 .Clone()
                 .Execute(action)
                 .Unload();
    }
    
    public static void Unload(this AppDomain domain)
    {
        AppDomain.Unload(domain);
    }

    public static AppDomain Clone(this AppDomain domain)
    {
        return domain.Clone(Guid.NewGuid().ToString());
    }

    public static AppDomain Clone(this AppDomain domain, string name)
    {
        AppDomainSetup setup = new AppDomainSetup();
        setup.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        setup.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
        setup.ShadowCopyFiles = "true";
        setup.ShadowCopyDirectories = setup.ApplicationBase;

        return AppDomain.CreateDomain(name, null, setup);
    }
}

public class RemoteExecutor : MarshalByRefObject
{
    Action action;

    public void Execute(Action action)
    {
        this.action = action;

        string asmFile = Assembly.GetExecutingAssembly().Location;
        using (AsmHelper helper = new AsmHelper(asmFile, null, false))
        {
            helper.Invoke(typeof(RemoteExecutor).FullName + ".ExecuteAction", this);
        }
    }

    static public void ExecuteAction(RemoteExecutor obj)
    {
        obj.action();
    }
}

public class RemoteDomain : MarshalByRefObject
{
    private void Do(Action action)
    {
        Console.WriteLine(AppDomain.CurrentDomain);
        action();
    }

    AppDomain executionDomain;

    private AppDomain ExecutionDomain
    {
        get
        {
            if (executionDomain == null)
            {
                AppDomainSetup setup = new AppDomainSetup();
                setup.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                setup.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
                setup.ShadowCopyFiles = "true";
                setup.ShadowCopyDirectories = setup.ApplicationBase;

                executionDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, setup);
            }
            return executionDomain;
        }
    }

    private void LoadAssembly(string path)
    {
        Assembly.LoadFrom(path);
    }

    public void Execute(Action action)
    {
        AppDomainSetup setup = new AppDomainSetup();
        setup.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        setup.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
        setup.ShadowCopyFiles = "true";
        setup.ShadowCopyDirectories = setup.ApplicationBase;
        var appDomain = AppDomain.CreateDomain("ttt", null, setup);

        var remote = (RemoteDomain)appDomain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(RemoteDomain).ToString());

        Stopwatch sw = new Stopwatch();
        sw.Start();
        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
        {
            if (asm is System.Reflection.Emit.AssemblyBuilder)
                continue;
            try
            {
                remote.LoadAssembly(asm.Location);
            }
            catch { }
        }
        sw.Stop();
        Console.WriteLine(sw.Elapsed);
        remote.Do(action);

        AppDomain.Unload(appDomain);
    }

    public static void ExecuteAndUnload(Action action)
    {
        AppDomainSetup setup = new AppDomainSetup();
        setup.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        setup.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
        setup.ShadowCopyFiles = "true";
        setup.ShadowCopyDirectories = setup.ApplicationBase;
        var appDomain = AppDomain.CreateDomain("ttt", null, setup);

        var remote = (RemoteDomain)appDomain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(RemoteDomain).ToString());

        Stopwatch sw = new Stopwatch();
        sw.Start();
        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
        {
            if (asm is System.Reflection.Emit.AssemblyBuilder)
                continue;
            try
            {
                remote.LoadAssembly(asm.Location);
            }
            catch { }
        }
        sw.Stop();
        Console.WriteLine(sw.Elapsed);
        remote.Do(action);

        AppDomain.Unload(appDomain);
    }
}